﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.AccountManagement;

namespace ADSync
{
	public class ADToolKit : IDisposable
	{
		private PrincipalContext placementContext;
		private PrincipalContext generalContext;
		private bool active;

		public List<ActionDetails> ActionLog { get; set; }

		public ADToolKit(bool act)
		{
			placementContext = new PrincipalContext(ContextType.Domain, "bsfcdomain", "OU=StudentGroups,DC=BSFCDOMAIN,DC=BSFC,DC=AC,DC=UK", null, null);
			generalContext = new PrincipalContext(ContextType.Domain, "bsfcdomain", "DC=BSFCDOMAIN,DC=BSFC,DC=AC,DC=UK", null, null);

			if (placementContext == null || generalContext == null)
				throw new Exception("Could not authenticate against Active Directory");
			active = act;
			ActionLog = new List<ActionDetails>();
		}

		/// <summary>
		/// Helper to ready group members from AD
		/// </summary>
		public List<string> QueryGroupMembers(string group)
		{
			List<string> users = new List<string>(30);
			using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(placementContext, group))
			{
				foreach (Principal p in groupPrincipal.Members)
				{
					UserPrincipal userp = p as UserPrincipal;
					if (userp == null) continue;

					users.Add(userp.SamAccountName.ToLower());
				}
			}

			return users;
		}

		public int CreateGroups(IEnumerable<string> groups, out List<string> created)
		{
			int c = 0;
			created = new List<string>(50);

			if (!active) return 0;

			foreach (string g in groups)
			{
				try
				{
					using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(placementContext, g))
					{
						if (groupPrincipal != null) continue;
					}

					using (GroupPrincipal newgroup = new GroupPrincipal(placementContext, g))
					{
						newgroup.Save();
						created.Add(g);
						++c;
					}
					ActionLog.Add(new ActionDetails(null, null, g, "New group", false));
				}
				catch (Exception ex)
				{
					ActionLog.Add(new ActionDetails(ex, null, g, "New group", true));
				}
			}

			return c;
		}

		public int AddUsersToGroup(IEnumerable<string> users, string group)
		{
			int c = 0;
			bool added = false;
			List<UserPrincipal> userlist = new List<UserPrincipal>();

			// Find the group
			using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(placementContext, group))
			{
				foreach (string u in users)
				{
					try
					{
						UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(generalContext, u);
						if (groupPrincipal.Members.Contains(generalContext, IdentityType.SamAccountName, u))
							continue;

						groupPrincipal.Members.Add(userPrincipal);
						userlist.Add(userPrincipal);
						added = true;
						++c;
						ActionLog.Add(new ActionDetails(null, u, group, "Added to group", false));
					}
					catch (Exception ex)
					{
						ActionLog.Add(new ActionDetails(ex, u, group, "Add to group", true));
					}
				}

				if (added && active)
					groupPrincipal.Save();
			}

			foreach (UserPrincipal u in userlist)
				u.Dispose();

			return c;
		}

		public int RemoveUsersFromGroup(IEnumerable<string> users, string group)
		{
			int c = 0;
			bool removed = false;
			List<UserPrincipal> userlist = new List<UserPrincipal>();

			// Find the group
			using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(placementContext, group))
			{
				foreach (string u in users)
				{
					try
					{
						UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(generalContext, u);
						if (!groupPrincipal.Members.Contains(generalContext, IdentityType.SamAccountName, u))
							continue;

						groupPrincipal.Members.Remove(userPrincipal);
						userlist.Add(userPrincipal);
						removed = true;
						++c;
						ActionLog.Add(new ActionDetails(null, u, group, "Removed from group", false));
					}
					catch (Exception ex)
					{
						ActionLog.Add(new ActionDetails(ex, u, group, "Remove from group", true));
					}
				}

				if (removed && active)
					groupPrincipal.Save();
			}

			foreach (UserPrincipal u in userlist)
				u.Dispose();

			return c;
		}

		public int RemoveGroups(IEnumerable<string> groups)
		{
			int c = 0;
			if (!active) return 0;

			foreach (string g in groups)
			{
				try
				{
					using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(placementContext, g))
					{
						if (groupPrincipal == null) continue;

						groupPrincipal.Delete();
						++c;
						ActionLog.Add(new ActionDetails(null, null, g, "Removed group", false));
					}
				}
				catch (Exception ex)
				{
					ActionLog.Add(new ActionDetails(ex, null, g, "Remove group", true));
				}
			}

			return c;
		}

		public void Dispose()
		{
			try
			{
				if (placementContext != null)
					placementContext.Dispose();
				if (generalContext != null)
					generalContext.Dispose();
			}
			finally
			{
				placementContext = null;
				generalContext = null;
			}
		}
	}

	/// <summary>
	/// Error details including exception and entity name
	/// </summary>
	public struct ActionDetails
	{
		public Exception Exception;
		public string User;
		public string Group;
		public string Action;
		public bool Error;

		public ActionDetails(Exception ex, string u, string grp, string act, bool err)
		{
			Exception = ex;
			User = u;
			Group = grp;
			Action = act;
			Error = err;
		}
	}
}
